package com.linln.admin.rsa;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.linln.admin.pcs.service.RsaService;
import com.msun.util.OpenapiUtil;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.neovisionaries.ws.client.WebSocketFrame;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * 标准接口调用工具方法
 */
public class ToInterfaceTool {
    private String appId;

    private String appSecret;
    private String hospitalId;
    private String orgId;
    private String loginUser;
    private String wsUrl;
    private String clientId;
    private String appName;
    private String busiName;
    private boolean storage;

    private static final int TIMEOUT = 5000;
    private static final String RSA256 = "RSA2";
    private static final String SM2 = "SM2";

    public void setKey(String appId, String appSecret) {
        this.appId = appId;
        this.appSecret = appSecret;
    }

    public void setHeaders(String hospitalId, String orgId, String loginUser) {
        this.hospitalId = hospitalId;
        this.orgId = orgId;
        this.loginUser = loginUser;
    }

    public void setWsUrl(String wsUrl, String clientId, String appName, String busiName, boolean storage) {
        this.wsUrl = wsUrl;
        this.clientId = clientId;
        this.appName = appName;
        this.busiName = busiName;
        this.storage = storage;
    }

    /**
     * 构建http请求头
     *
     * @param sign
     * @param timestamp
     * @return
     */
    public Map<String, String> builderHeaders(String sign, long timestamp) {
        Map<String, String> headersMap = new HashMap<>();
        String keyType = checkAppSecret(appSecret);
        String license = OpenapiUtil.getLicense(appId, appSecret, keyType, String.valueOf(timestamp));
        headersMap.put("appId", appId);
        headersMap.put("signType", keyType);
        headersMap.put("orgId", orgId);
        headersMap.put("hospitalId", hospitalId);
        headersMap.put("sign", sign);
        headersMap.put("timestamp", timestamp + "");
        headersMap.put("license", license);
        if (StringUtils.isNotEmpty(loginUser)) {
            headersMap.put("loginUser", loginUser);
        }
        return headersMap;
    }

    /**
     * POST请求
     *
     * @param url       请求接口地址
     * @param paramsMap 请求参数，可以为空
     * @return
     * @throws Exception
     */
    public String toPost(String url, Map<String, Object> paramsMap) throws Exception {

        // 参数不为空，转换成标准JSON字符串
        String paramsJsonStr = "";
        if (CollectionUtil.isNotEmpty(paramsMap)) {
            paramsJsonStr = JSON.toJSONString(paramsMap);
        }
        // 当前时间戳，与header头中的timestamp一致
        long timestamp = System.currentTimeMillis();

        // 待MD5加密字符串 = 请求字符串 + 当前时间戳
        String signatureStr = paramsJsonStr + timestamp;
        // MD5进行摘要加密
        MessageDigest md = MessageDigest.getInstance("MD5");
        String md5Str = Hex.encodeHexString(md.digest(signatureStr.getBytes(StandardCharsets.UTF_8)));
        String keyType = checkAppSecret(appSecret);
        // 根据MD5摘要进行签名
        String sign = getSign(md5Str, appSecret, null, keyType);
        // 构建请求头
        Map<String, String> headers = builderHeaders(sign, timestamp);
        // 构建请求，可以用RestTemplate、HttpClient、  WebFlux等工具类，具体看自身项目工具类使用，不做限制
        HttpRequest postHttpReq = HttpUtil.createPost(url);
        // 加入定义的请求头
        postHttpReq.addHeaders(headers);
        // 加入请求参数，转换成JSON后的
        postHttpReq.body(paramsJsonStr);
        // 发起请求
        HttpResponse response = postHttpReq.execute();
        // 返回结果body
        String body = response.body();
        return body;
    }

    /**
     * GET请求
     *
     * @param url       请求接口地址
     * @param paramsMap 请求参数，可以为空
     * @return
     */
    public String toGet(String url, Map<String, Object> paramsMap) throws Exception {
        // 请求参数不为空，按照ASCII码自然排序
        Map<String, Object> getParamsMap = new TreeMap<>(Comparator.naturalOrder());
        if (CollectionUtil.isNotEmpty(paramsMap)) {
            paramsMap.entrySet().forEach(n -> getParamsMap.put(n.getKey(), n.getValue()));
        }
        // 排序后的请求参数字符串
        String sortedParamsStr = "";
        if (CollectionUtil.isNotEmpty(getParamsMap)) {
            Set<String> keySet = getParamsMap.keySet();
            Iterator<String> iter = keySet.iterator();
            StringBuilder sb = new StringBuilder();
            while (iter.hasNext()) {
                String key = iter.next();
                sb.append(key).append("=").append(getParamsMap.get(key)).append("&");
            }
            sortedParamsStr = sb.deleteCharAt(sb.length() - 1).toString();
        }
        // 当前时间戳，与header头中的timestamp一致
        long timestamp = System.currentTimeMillis();
        // 待签名字符串 = 排序后请求字符串 + 当前时间戳
        String signatureStr = sortedParamsStr + timestamp;
        String keyType = checkAppSecret(appSecret);
        // 执行签名
        String sign = getSign(signatureStr, appSecret, null, keyType);
        // 构建请求头
        Map<String, String> headers = builderHeaders(sign, timestamp);
        // 有请求参数，拼接到地址栏，比  如 https://openapi.msunhis.com/msun-his-app-emr-openapi/v1/list?a=1&c=3&b=2
        if (StringUtils.isNotBlank(sortedParamsStr)) {
            url = url + "?" + sortedParamsStr;
        }
        // 构建请求，可以用RestTemplate、HttpClient、 WebFlux等工具类，具体看自身项目工具类使用，不做限制
        HttpRequest getHttpReq = HttpUtil.createGet(url);
        // 加入定义的请求头
        getHttpReq.addHeaders(headers);
        // 发起请求
        HttpResponse response = getHttpReq.execute();
        // 返回结果body
        String body = response.body();
        return body;
    }


    /**
     * GET请求url传参格式
     *
     * @param url 请求接口地址
     * @return
     */
    public String toGetUrlParam(String url) throws Exception {
        // 当前时间戳，与header头中的timestamp一致
        long timestamp = System.currentTimeMillis();

        // 待签名字符串 = 排序后请求字符串 + 当前时间戳
        String signatureStr = "" + timestamp;

        // 执行签名
        String keyType = checkAppSecret(appSecret);
        String sign = getSign(signatureStr, appSecret, null, keyType);

        // 构建请求头
        Map<String, String> headers = builderHeaders(sign, timestamp);

        // 构建请求，可以用RestTemplate、HttpClient、 WebFlux等工具类，具体看自身项目工具类使用，不做限制
        HttpRequest getHttpReq = HttpUtil.createGet(url);
        // 加入定义的请求头
        getHttpReq.addHeaders(headers);
        // 发起请求
        HttpResponse response = getHttpReq.execute();
        // 返回结果body
        String body = response.body();
        return body;
    }

    /**
     * 检查appSecret是RSA256还是SM2
     * @param appSecret 私钥字符串 如果是RSA256则是BASE64编码 如果是SM2则是HEX
     * @return
     */
    public static String checkAppSecret(String appSecret) {
        //判断是不是hex
        boolean hex = appSecret.matches("[0-9A-Fa-f]+");
        if (!hex) {
            return "RSA2";
        } else {
            return "SM2";
        }
    }

    /**
     * 生成签名
     *
     * @param preStr    待签名字符串
     * @param appSecret 私钥字符串
     * @return base64字符串
     * @throws Exception
     */
    public static String getSign(String preStr, String appSecret, String isWs, String keyType) throws Exception {
        if (RSA256.equals(keyType)) {
            return getRsaSign(preStr, appSecret, isWs);
        }
        if (SM2.equals(keyType)) {
            return Sm2Util.sign(preStr, appSecret, isWs);
        }
        return "";
    }

    private static String getRsaSign(String preStr, String appSecret, String isWs) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, SignatureException {
        appSecret = appSecret.replace("-----    BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace("\r", "").replace("\n", "").trim();
        String suite = "SHA256WithRSA";

        //初始化算法SHA256
        Signature signature = Signature.getInstance(suite);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(appSecret));
        //初始化私钥+RSA
        KeyFactory keyFac = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFac.generatePrivate(keySpec);
        signature.initSign(privateKey);

        //待签名字符串转byte数组使用UTF8
        byte[] msgBuf = preStr.getBytes("UTF8");
        signature.update(msgBuf);
        byte[] byteSign = signature.sign();
        //签名值byte数组转字符串用BASE64
        String strSign = "";
        if ("1".equals(isWs)) {
            strSign = Base64.encodeBase64String(byteSign);
            System.out.println("wsSign = " + strSign);
            return strSign.replace("+", "-")
                    .replace("/", "_")
                    .replace("=", "");
        } else {
            strSign = Base64.encodeBase64String(byteSign);
        }
        System.out.println("strSign = " + strSign);
        return strSign;
    }

    /**
     * description: 生成websocket请求问号后的URL
     *
     * @return: java.lang.String
     */
    String getWSUrl() throws Exception {
        long timestamp = System.currentTimeMillis();
        // 待签名字符串 = 排序后请求字符串 + 当前时间戳
        String signatureStr = timestamp + "";
        StringBuilder stringBuilder = new StringBuilder(wsUrl);
        String keyType = checkAppSecret(appSecret);
        String license = OpenapiUtil.getLicense(appId, appSecret, keyType, signatureStr).replace("+", "-")
                .replace("/", "_")
                .replace("=", "");

        String sign = getSign(signatureStr, appSecret, "1", keyType);
        System.out.println("sign = " + sign);

        stringBuilder.append("?clientId=").append(clientId)
                .append("&appName=").append(appName)
                .append("&busiName=").append(busiName)
                .append("&appId=").append(appId)
                .append("&hospitalId=").append(hospitalId)
                .append("&orgId=").append(orgId)
                .append("&signType=").append(keyType)
                .append("&timestamp=").append(signatureStr)
                .append("&sign=").append(sign)
                .append("&license=").append(license)
                .append("&storage=").append(storage);

        return stringBuilder.toString();
    }

    /**
     * description: 连接websocket服务端
     * 并设置自动重连
     *
     * @return: void
     */
    public void connectWS() throws Exception {
        String wsUrlResult = getWSUrl();
        System.out.println("wsUrlResult:" + wsUrlResult);
        WebSocket ws = new WebSocketFactory().setConnectionTimeout(TIMEOUT).createSocket(wsUrlResult);
        ws.addListener(new WebSocketAdapter() {
            /**
             * create time: 2022/8/8 16:47
             * description:  接收服务端推送的消息
             * @param websocket:
             * @param text:
             * @return: void 20.6
             */
            @Override
            public void onTextMessage(WebSocket websocket, String text) throws Exception {
                System.out.println("websocketReceive==" + text);
                if (StringUtils.isNotBlank(text)) {
                    //第三方厂家接收到消息后的业务处理
                }
            }

            /**
             * description: websocket断开后需要重连，默认60s断开
             * @param websocket:
             * @param serverCloseFrame:
             * @param clientCloseFrame:
             * @param closedByServer:
             * @return: void
             */
            @Override
            public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception {
                System.out.println("===========onDisconnected==============");
                //自动重连，以下方法仍然几秒后就断开，需要做重连工作，demo仅做演示
                ws.recreate().connect();
            }
        }).connect();
    }
}
